#region Utf8Json License https://github.com/neuecc/Utf8Json/blob/master/LICENSE
// MIT License
//
// Copyright (c) 2017 Yoshifumi Kawai
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#endregion

using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace Nest.Utf8Json
{
	internal struct ArgumentField
	{
		private readonly int _i;
		private readonly bool _ref;
		private readonly ILGenerator _il;

		public ArgumentField(ILGenerator il, int i, bool @ref = false)
		{
			_il = il;
			_i = i;
			_ref = @ref;
		}

		public ArgumentField(ILGenerator il, int i, Type type)
		{
			_il = il;
			_i = i;
			_ref = !type.IsClass && !type.IsInterface && !type.IsAbstract;
		}

		public void EmitLoad()
		{
			if (_ref)
			{
				_il.EmitLdarga(_i);
			}
			else
			{
				_il.EmitLdarg(_i);
			}
		}

		public void EmitStore() => _il.EmitStarg(_i);
	}

	/// <summary>
	/// Provides optimized generation code and helpers.
	/// </summary>
	// ReSharper disable once InconsistentNaming
	internal static class ILGeneratorExtensions
	{
		/// <summary>
		/// Loads the local variable at a specific index onto the evaluation stack.
		/// </summary>
		public static void EmitLdloc(this ILGenerator il, int index)
		{
			switch (index)
			{
				case 0:
					il.Emit(OpCodes.Ldloc_0);
					break;
				case 1:
					il.Emit(OpCodes.Ldloc_1);
					break;
				case 2:
					il.Emit(OpCodes.Ldloc_2);
					break;
				case 3:
					il.Emit(OpCodes.Ldloc_3);
					break;
				default:
					if (index <= 255)
						il.Emit(OpCodes.Ldloc_S, (byte)index);
					else
						il.Emit(OpCodes.Ldloc, (short)index);
					break;
			}
		}

		public static void EmitLdloc(this ILGenerator il, LocalBuilder local) => EmitLdloc(il, local.LocalIndex);

		/// <summary>
		/// Pops the current value from the top of the evaluation stack and stores it in a the local variable list at a specified index.
		/// </summary>
		public static void EmitStloc(this ILGenerator il, int index)
		{
			switch (index)
			{
				case 0:
					il.Emit(OpCodes.Stloc_0);
					break;
				case 1:
					il.Emit(OpCodes.Stloc_1);
					break;
				case 2:
					il.Emit(OpCodes.Stloc_2);
					break;
				case 3:
					il.Emit(OpCodes.Stloc_3);
					break;
				default:
					if (index <= 255)
					{
						il.Emit(OpCodes.Stloc_S, (byte)index);
					}
					else
					{
						il.Emit(OpCodes.Stloc, (short)index);
					}
					break;
			}
		}

		public static void EmitStloc(this ILGenerator il, LocalBuilder local) => EmitStloc(il, local.LocalIndex);

		/// <summary>
		/// Loads the address of the local variable at a specific index onto the evaluation statck.
		/// </summary>
		public static void EmitLdloca(this ILGenerator il, int index)
		{
			if (index <= 255)
				il.Emit(OpCodes.Ldloca_S, (byte)index);
			else
				il.Emit(OpCodes.Ldloca, (short)index);
		}

		public static void EmitLdloca(this ILGenerator il, LocalBuilder local) => EmitLdloca(il, local.LocalIndex);

		public static void EmitTrue(this ILGenerator il) => EmitBoolean(il, true);

		public static void EmitFalse(this ILGenerator il) => EmitBoolean(il, false);

		private static void EmitBoolean(this ILGenerator il, bool value) => EmitLdc_I4(il, value ? 1 : 0);

		/// <summary>
		/// Pushes a supplied value of type int32 onto the evaluation stack as an int32.
		/// </summary>
		public static void EmitLdc_I4(this ILGenerator il, int value)
		{
			switch (value)
			{
				case -1:
					il.Emit(OpCodes.Ldc_I4_M1);
					break;
				case 0:
					il.Emit(OpCodes.Ldc_I4_0);
					break;
				case 1:
					il.Emit(OpCodes.Ldc_I4_1);
					break;
				case 2:
					il.Emit(OpCodes.Ldc_I4_2);
					break;
				case 3:
					il.Emit(OpCodes.Ldc_I4_3);
					break;
				case 4:
					il.Emit(OpCodes.Ldc_I4_4);
					break;
				case 5:
					il.Emit(OpCodes.Ldc_I4_5);
					break;
				case 6:
					il.Emit(OpCodes.Ldc_I4_6);
					break;
				case 7:
					il.Emit(OpCodes.Ldc_I4_7);
					break;
				case 8:
					il.Emit(OpCodes.Ldc_I4_8);
					break;
				default:
					if (value >= -128 && value <= 127)
						il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
					else
						il.Emit(OpCodes.Ldc_I4, value);
					break;
			}
		}

		public static void EmitUnboxOrCast(this ILGenerator il, Type type) =>
			il.Emit(type.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, type);

		public static void EmitBoxOrDoNothing(this ILGenerator il, Type type)
		{
			if (type.IsValueType)
				il.Emit(OpCodes.Box, type);
		}

		public static void EmitLdarg(this ILGenerator il, int index)
		{
			switch (index)
			{
				case 0:
					il.Emit(OpCodes.Ldarg_0);
					break;
				case 1:
					il.Emit(OpCodes.Ldarg_1);
					break;
				case 2:
					il.Emit(OpCodes.Ldarg_2);
					break;
				case 3:
					il.Emit(OpCodes.Ldarg_3);
					break;
				default:
					if (index <= 255)
						il.Emit(OpCodes.Ldarg_S, (byte)index);
					else
						il.Emit(OpCodes.Ldarg, index);
					break;
			}
		}

		public static void EmitLoadThis(this ILGenerator il) => EmitLdarg(il, 0);

		public static void EmitLdarga(this ILGenerator il, int index)
		{
			if (index <= 255)
				il.Emit(OpCodes.Ldarga_S, (byte)index);
			else
				il.Emit(OpCodes.Ldarga, index);
		}

		public static void EmitStarg(this ILGenerator il, int index)
		{
			if (index <= 255)
				il.Emit(OpCodes.Starg_S, (byte)index);
			else
				il.Emit(OpCodes.Starg, index);
		}

		/// <summary>
		/// Helper for Pop op.
		/// </summary>
		public static void EmitPop(this ILGenerator il, int count)
		{
			for (var i = 0; i < count; i++)
				il.Emit(OpCodes.Pop);
		}

		public static void EmitCall(this ILGenerator il, MethodInfo methodInfo)
		{
			if (methodInfo.IsFinal || !methodInfo.IsVirtual)
				il.Emit(OpCodes.Call, methodInfo);
			else
				il.Emit(OpCodes.Callvirt, methodInfo);
		}

		public static void EmitLdfld(this ILGenerator il, FieldInfo fieldInfo) => il.Emit(OpCodes.Ldfld, fieldInfo);

		public static void EmitLdsfld(this ILGenerator il, FieldInfo fieldInfo) => il.Emit(OpCodes.Ldsfld, fieldInfo);

		public static void EmitRet(this ILGenerator il) => il.Emit(OpCodes.Ret);

		public static void EmitIntZeroReturn(this ILGenerator il)
		{
			il.EmitLdc_I4(0);
			il.Emit(OpCodes.Ret);
		}

		public static void EmitNullReturn(this ILGenerator il)
		{
			il.Emit(OpCodes.Ldnull);
			il.Emit(OpCodes.Ret);
		}

		public static void EmitULong(this ILGenerator il, ulong value) => il.Emit(OpCodes.Ldc_I8, unchecked((long)value));

		public static void EmitThrowNotimplemented(this ILGenerator il)
		{
			il.Emit(OpCodes.Newobj, typeof(NotImplementedException).GetDeclaredConstructors().First(x => x.GetParameters().Length == 0));
			il.Emit(OpCodes.Throw);
		}

		/// <summary>for  var i = 0, i ..., i++ </summary>
		public static void EmitIncrementFor(this ILGenerator il, LocalBuilder conditionGreater, Action<LocalBuilder> emitBody)
		{
			var loopBegin = il.DefineLabel();
			var condtionLabel = il.DefineLabel();

			// var i = 0
			var forI = il.DeclareLocal(typeof(int));
			il.EmitLdc_I4(0);
			il.EmitStloc(forI);
			il.Emit(OpCodes.Br, condtionLabel);

			il.MarkLabel(loopBegin);
			emitBody(forI);

			// i++
			il.EmitLdloc(forI);
			il.EmitLdc_I4(1);
			il.Emit(OpCodes.Add);
			il.EmitStloc(forI);

			//// i < ***
			il.MarkLabel(condtionLabel);
			il.EmitLdloc(forI);
			il.EmitLdloc(conditionGreater);
			il.Emit(OpCodes.Blt, loopBegin);
		}
	}
}
